Доопрацювання моделі Post
=========================

Модель `Post`, згенерована за допомогою `Gii`, потребує наступних змін:

 - метод `rules()`: задає правила валідації атрибутів моделі;
 - метод `relations()`: задає звʼязки з іншими обʼєктами.

> Info|Інформація: [Модель](/doc/guide/uk/basics.model) складається із набору атрибутів,
кожен з яких асоціюється з відповідним полем у таблиці БД. Атрибути можуть бути описані
явно як змінні класу, або використовуватися без будь-якого опису.

Зміна методу `rules()`
----------------------

У першу чергу необхідно визначити правила валідації, які дозволять переконатися в тому,
що дані, введені користувачем, коректні до їх збереження в БД.
Наприклад, атрибут `status` моделі `Post` повинен бути цілим числом, рівним 1, 2 або 3.
Консоль `Gii` генерує правила валідації для кожної моделі.
При цьому використовується структура БД, 
тому деякі правила можуть виявитися неточними.

Грунтуючись на аналізі вимог, змінимо метод `rules ()` наступним чином:

~~~
[php]
public function rules()
{
	return array(
		array('title, content, status', 'required'),
		array('title', 'length', 'max'=>128),
		array('status', 'in', 'range'=>array(1,2,3)),
		array('tags', 'match', 'pattern'=>'/^[\w\s,]+$/',
			'message'=>'У тегах можна використовувати лише літери.'),
		array('tags', 'normalizeTags'),

		array('title, status', 'safe', 'on'=>'search'),
	);
}
~~~

У коді вище ми визначили, що атрибути `title`, `content` і `status`
є обовʼязковими для заповнення. Довжина `title` не повинна перевищувати 128 символів.
Значення `status` може бути 1 (чернетка), 2 (опубліковано) або 3 (в архіві). 
В `tags` можуть міститися тільки букви, коми та пробіли. 
Теги, що вводяться користувачем, додатково нормалізуються за допомогою `normalizeTags`. 
Це робиться для того, щоб теги були унікальними і правильно розділялися комами.
Останнє правило використовується пошуком і буде описано пізніше.

Валідатори, такі як `required`, `length`, `in` та `match` є стандартними валідаторами Yii.
Валідатор `normalizeTags` використовує визначений метод у класі `Post`.
За додатковою інформацією про те, як описувати правила валідації ви можете звернутися до [повного керівництва](/doc/guide/uk/form.model#declaring-validation-rules).

~~~
[php]
public function normalizeTags($attribute,$params)
{
	$this->tags=Tag::array2string(array_unique(Tag::string2array($this->tags)));
}
~~~

де `array2string` та `string2array` - нові методи,
які ми повинні визначити у класі моделі `Tag`:

~~~
[php]
public static function string2array($tags)
{
	return preg_split('/\s*,\s*/',trim($tags),-1,PREG_SPLIT_NO_EMPTY);
}

public static function array2string($tags)
{
	return implode(', ',$tags);
}
~~~

Правила, описані у методі `rules()`, викликаються по черзі при виклиці методів моделі
[validate()|CModel::validate] або [save()|CActiveRecord::save].

> Note|Примітка: Важливо памʼятати, що атрибути, описувані у `rules()` повинні
вводитися користувачем. Інші атрибути моделі `Post`, такі як `id` або `create_time`,
що заповнюються у коді або безпосередньо у БД, не повинні бути присутніми в `rules()`.
Детальніше це описано у розділі [Безпечне присвоювання значень атрибутів](/doc/guide/uk/form.model#securing-attribute-assignments).

Після того, як ми зробили описані зміни, ми можемо зайти на сторінку створення запису 
і перевірити, що нові правила валідації працюють.

Зміна методу `relations()`
--------------------------

Далі вкажемо у методі `relations()` звʼязані із записом обʼєкти. Після цього ми
зможемо використовувати [реляційну ActiveRecord (RAR)](/doc/guide/uk/database.arr)
для отримання звʼязаних із записом даних, таких як інформацію про автора та коментарі. Складні SQL запити з JOIN в цьому випадку не потрібні.

Визначимо метод `relations()`:

~~~
[php]
public function relations()
{
	return array(
		'author' => array(self::BELONGS_TO, 'User', 'author_id'),
		'comments' => array(self::HAS_MANY, 'Comment', 'post_id',
			'condition'=>'comments.status='.Comment::STATUS_APPROVED,
			'order'=>'comments.create_time DESC'),
		'commentCount' => array(self::STAT, 'Comment', 'post_id',
			'condition'=>'status='.Comment::STATUS_APPROVED),
	);
}
~~~

Також, у класі моделі `Comment` ми описуємо дві константи, які використовуються у наведеному вище методі:

~~~
[php]
class Comment extends CActiveRecord
{
	const STATUS_PENDING=1;
	const STATUS_APPROVED=2;
	......
}
~~~

Звʼязки, описані у методі `relations ()`, означають наступне:

 * Запис належить автору (`User`), звʼязок із яким встановлюється на основі поля 
запису `author_id`;
 * Запис може містити багато коментарів (`Comment`), звʼязок із якими встановлюється на основі поля коментаря `post_id`. Коментарі сортуються за часом їх створення;
 * Звʼязок `commentCount` є особливим, оскільки повертає результат агрегації, тобто число коментарів запису.

Задавши описані вище звʼязки, ми можемо отримати інформацію про автора 
та коментарі до запису наступним чином:

~~~
[php]
$author=$post->author;
echo $author->username;

$comments=$post->comments;
foreach($comments as $comment)
	echo $comment->content;
~~~

Більш докладно використання та визначення звʼязків описано у
[повному керівництві](/doc/guide/uk/database.arr).

Додаємо властивість `url`
-------------------------

Кожному запису відповідає унікальний URL. 
Замість повсюдного виклику [CWebApplication::createUrl] для формування цього URL, 
ми можемо додати властивість `url` моделі `Post` і повторно використовувати 
код для генерації URL. Пізніше ми опишемо, як отримати гарні URL. 
Використання властивості моделі дозволить реалізувати це максимально зручно.

Для того, щоб додати властивість `url`, ми додаємо геттер у клас `Post`:

~~~
[php]
class Post extends CActiveRecord
{
	public function getUrl()
	{
		return Yii::app()->createUrl('post/view', array(
			'id'=>$this->id,
			'title'=>$this->title,
		));
	}
}
~~~

На додаток до ID запису, в URL через GET-параметр ми виводимо заголовок.
Робиться це головним чином для оптимізації під пошукові алгоритми (SEO). 
Детальніше це буде описано в розділі «[людинозрозумілі URL](/doc/blog/final.url)».

Так як [CComponent] є предком класу `Post`, геттер `getUrl()` дозволяє нам 
писати код на зразок `$post->url`. При зверненні до `$post->url` буде викликаний 
геттер і ми отримаємо результат його виконання. 
Більш докладно це описано у [повному керівництві](/doc/guide/uk/basics.component).

Текстове представлення для статусу
----------------------------------

Так як статус запису зберігається у БД у вигляді числа, нам необхідно отримати його
текстове представлення для відображення користувачам. Для великих систем така
вимога є досить типовою.

Для зберігання звʼязків між цілими числами і їх текстовим поданням, необхідним
іншим обʼєктам даних, ми використовуємо таблицю `tbl_lookup`. Для більш зручного
отримання текстових даних змінимо модель `Lookup` наступним чином:

~~~
[php]
class Lookup extends CActiveRecord
{
	…

	private static $_items=array();

	public static function items($type)
	{
		if(!isset(self::$_items[$type]))
			self::loadItems($type);
		return self::$_items[$type];
	}

	public static function item($type,$code)
	{
		if(!isset(self::$_items[$type]))
			self::loadItems($type);
		return isset(self::$_items[$type][$code]) ? self::$_items[$type][$code] : false;
	}

	private static function loadItems($type)
	{
		self::$_items[$type]=array();
		$models=self::model()->findAll(array(
			'condition'=>'type=:type',
			'params'=>array(':type'=>$type),
			'order'=>'position',
		));
		foreach($models as $model)
			self::$_items[$type][$model->code]=$model->name;
	}
}
~~~

Ми додали два статичних методи: `Lookup::items()` та `Lookup::item()`.
Перший повертає список рядків для заданого типу даних, другий - конкретний
рядок для заданого типу даних і значення.

У базі даних блогу є два типи даних: `PostStatus` та `CommentStatus`.
Перший містить можливі статуси запису, другий - статуси коментаря.

Для того, щоб зробити код більш читабельним ми описуємо константи, відповідні
цілочисловим значенням статусу. Ці константи необхідно використовувати у коді
замість відповідних їм цілих значень.

~~~
[php]
class Post extends CActiveRecord
{
	const STATUS_DRAFT=1;
	const STATUS_PUBLISHED=2;
	const STATUS_ARCHIVED=3;
	......
}
~~~

Отже, для отримання списку всіх можливих статусів запису (масиву рядків
із ключами, рівними відповідним їм значенням), ми можемо скористатися кодом
`Lookup::items('PostStatus')`. А для отримання конкретного рядка - кодом
`Lookup::item('PostStatus', Post::STATUS_PUBLISHED)`.
